package framework.elements;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.ElementNotVisibleException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Action;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;

import com.gargoylesoftware.htmlunit.javascript.background.JavaScriptExecutor;

import framework.BaseAutoIt;
import framework.BaseEntity;
import framework.Browser;
import framework.Logger;

/**
 * Абстрактный класс, описывающий базовый элемент (контрол) на странице Содержит
 * общие для всех остальных элементов методы
 */
public abstract class BaseElement extends BaseEntity {

	private static final String LINK = "link=";
	private static final String ID = "id=";
	private static final String CSS = "css=";
	protected String name; // локатор и имя элемента
	protected By locator;
	protected RemoteWebElement element;

	public RemoteWebElement getElement() {
		waitForIsElementPresent();
		return element;
	}

	public void setElement(RemoteWebElement element) {
		this.element = element;
	}

	/**
	 * Проверка на то, что раскрывающийся элемент свернут (выполняется на основе
	 * класс элемента)
	 * 
	 * @return
	 */
	public boolean isCollapsed() {
		waitAndAssertIsPresent();
		String elementClass = element.getAttribute("class");
		return elementClass.toLowerCase().contains("collapse");
	}

	public void blur() {
		try {
			browser.getDriver().findElementByTagName("body").click();
		} catch (ElementNotVisibleException e) {
			browser.getDriver().switchTo().activeElement().sendKeys(Keys.TAB);
		}
	}

	/**
	 * Проверка на то, что элемент активен (выполняется на основе класс
	 * элемента)
	 * 
	 * @return
	 */
	public boolean isEnabled() {
		waitAndAssertIsPresent();
		String elementClass = element.getAttribute("class");
		return element.isEnabled()
				&& (!elementClass.toLowerCase().contains("disabled"));
	}

	/**
	 * Проверяет что поле имеет аттрибут "disabled"
	 * 
	 * @return true если элемент disabled
	 */
	public boolean isDisabled() {
		waitAndAssertIsPresent();
		String disabled = element.getAttribute("disabled");
		if (disabled.equals(null) || disabled.equals("false")) {
			disabled = element.getAttribute("readOnly");
		}
		return disabled.equals("") || disabled.equals("disabled")
				|| disabled.equals("true");
	}

	/**
	 * Проверяет, что элемент содержит аттрибут "disabled" или "readonly"
	 */
	public void assertDisabled() {
		waitAndAssertIsPresent();
		String[] attributes = new String[] { "disabled", "readonly", "readOnly" };
		String disabled;
		for (int i = 0; i < attributes.length; i++) {
			disabled = element.getAttribute(attributes[i]);
			if (disabled.equals("") || disabled.equals("disabled")
					|| disabled.equals("true") || disabled.equals("readonly")) {
				return;
			}
		}
		fatal(getElementType() + " " + name
				+ " является доступным для редактирования!");
	}

	/**
	 * Проверка на то, что элемент выбран (выполняется на основе класс элемента)
	 * 
	 * @return
	 */
	public boolean isChecked() {
		waitAndAssertIsPresent();
		String elementClass = element.getAttribute("class");
		return !elementClass.toLowerCase().contains("unchecked");
	}

	public void clickAndApproveSecurityWarning(int warningCount) {
		waitAndAssertIsPresent();
		assertIsPresent();
		info("Клик");
		element.click();
		BaseAutoIt.approveSecurityWarning(warningCount);
	}

	public void clickAndApproveSecurityWarningAndSwitchToPreviousWindow(
			int warningCount) {
		int count = browser.getDriver().getWindowHandles().size();
		waitAndAssertIsPresent();
		assertIsPresent();
		info("Клик");
		element.click();
		BaseAutoIt.approveSecurityWarning(warningCount);
		info("Переключение на предыдущее окно");
		browser.waitForWindowIsCloseAndSwitch(count);
	}

	protected BaseElement() {
	}

	/**
	 * Основной конструктор
	 * 
	 * @param locator
	 * 
	 * @param name
	 * 
	 */
	protected BaseElement(final By locator, final String name) {
		this.locator = locator;
		this.name = name;
	}

	/**
	 * easy adapting from Selenium RC locators. CSS, ID, LINK
	 * 
	 * @param stringLocator
	 * @param name
	 */
	protected BaseElement(final String stringLocator, final String name) {
		String clearLoc = null;
		if (stringLocator.contains(CSS)) {
			// logger.info("detecting css locator");
			clearLoc = stringLocator.replaceFirst(CSS, "");
			this.locator = By.cssSelector(clearLoc);
			this.name = name;
		} else if (stringLocator.contains(ID)) {
			// logger.info("detecting id locator");
			clearLoc = stringLocator.replaceFirst(ID, "");
			this.locator = By.id(clearLoc);
			this.name = name;
		} else if (stringLocator.contains(LINK)) {
			// logger.info("detecting link locator");
			clearLoc = stringLocator.replaceFirst(LINK, "");
			this.locator = By.linkText(clearLoc);
			this.name = name;
		} else {
			logger.fatal("UNKNOWN LOCATOR's TYPE. Change to 'By'");
		}

	}

	public By getLocator() {
		return locator;
	}

	public String getName() {
		return name;
	}

	/**
	 * Реализация абстрактного метода для логирования из BaseEntity
	 * 
	 * @param message
	 *            Сообщение для вывода в лог
	 * @return Отформатированное сообщение (содержит имя и тип элемента)
	 */
	protected String formatLogMsg(final String message) {
		return String.format("%1$s '%2$s' %3$s %4$s", getElementType(), name,
				Logger.LOG_DELIMITER, message);
	}

	/**
	 * Метод, возвращающий тип элемента (используется для логирования)
	 */
	protected abstract String getElementType();

	/**
	 * Ждем появления элемента и кликает по нему. Если элемент не появился -
	 * тест завершается
	 */
	public void waitAndClick() {
		waitForIsElementPresent();
		assertIsPresent();
		info("Клик");
		element.click();
	}

	/**
	 * ext click through .sendKeys("\n");
	 */
	public void clickExt() {
		info("Клик расширенный");
		browser.getDriver().findElement(locator).sendKeys("\n");
	}

	/**
	 * Фокусируется на элементе посредством ввод пробела, а после нажимает на
	 * элемент
	 * 
	 */
	public void focusAndClick() {
		browser.getDriver().findElement(locator).sendKeys(" ");
		browser.getDriver().findElement(locator).click();
	}

	/**
	 * Фокусируется на элементе посредством ввод пробела
	 * 
	 */
	public void focus() {
		browser.getDriver().findElement(locator).sendKeys(" ");
	}

	/**
	 * Кликает по элементу в try операторе Обычно нужно, если возникает
	 * исключение "Element not attached to DOM"
	 * 
	 */
	public void clickThrowException() {
		try {
			click();
		} catch (Exception e) {
			// info(e.getMessage());
		}
		info("Клик");
	}

	/**
	 * Ждем пока элемент появится на странице, если он не появился в течении
	 * заданного времени, то тест завершается
	 */
	public void waitForIsElementPresent() {
		WebDriverWait wait = new WebDriverWait(browser.getDriver(),
				Long.parseLong(browser.getTimeoutForCondition()));

		try {
			wait.until((ExpectedCondition<Boolean>) new ExpectedCondition<Boolean>() {
				public Boolean apply(WebDriver driver) {
					try {
						List<WebElement> list = driver.findElements(locator);
						for (WebElement el : list) {
							if (el instanceof RemoteWebElement
									&& el.isDisplayed()) {
								element = (RemoteWebElement) el;
								if (element.isDisplayed())
									return element.isDisplayed();
							}
						}
						element = (RemoteWebElement) driver
								.findElement(locator);
					} catch (Exception e) {
						return false;
					}
					return element.isDisplayed();
				}
			});
		} catch (Exception e) {
			boolean res = false;
			try {
				res = (element != null) && (element.isDisplayed());
			} catch (Exception e1) {
			}
			Assert.assertTrue(formatLogMsg(" отсутствует"), res);
		}
		Assert.assertTrue(formatLogMsg(" отсутствует"), element.isDisplayed());
	}

	/**
	 * Ждем пока элемент появится на странице
	 * 
	 * @return true если дождался элемента
	 */
	public final Boolean waitForIsElementWithoutAssertion() {
		Boolean isPresent = true;
		WebDriverWait wait = new WebDriverWait(browser.getDriver(), 5l);
		try {
			wait.until((ExpectedCondition<Boolean>) new ExpectedCondition<Boolean>() {
				public Boolean apply(final WebDriver driver) {
					try {
						List<WebElement> list = driver.findElements(locator);
						for (WebElement el : list) {
							if (el instanceof RemoteWebElement
									&& el.isDisplayed()) {
								element = (RemoteWebElement) el;
								if (element.isDisplayed()) {
									return element.isDisplayed();
								}
							}
						}
						element = (RemoteWebElement) driver
								.findElement(locator);
					} catch (Exception e) {
						return false;
					}
					return element.isDisplayed();
				}
			});
		} catch (Exception e) {
			isPresent = false;
		}
		return isPresent;
	}

	/**
	 * Ожидаем, пока элемент не существует (нет проверки на Visibility)
	 */
	public void waitNotVisible() {
		WebDriverWait wait = new WebDriverWait(browser.getDriver(),
				Long.parseLong(browser.getTimeoutForCondition()));

		try {
			wait.until((ExpectedCondition<Boolean>) new ExpectedCondition<Boolean>() {
				public Boolean apply(WebDriver driver) {
					try {
						List<WebElement> list = driver.findElements(locator);
						for (WebElement el : list) {
							if (el instanceof RemoteWebElement) {
								element = (RemoteWebElement) el;
								return true;
							}
						}
						element = (RemoteWebElement) driver
								.findElement(locator);
						return true;
					} catch (Exception e) {
						return false;
					}
				}
			});
		} catch (Exception e) {
			boolean res = false;
			try {
				res = (element != null);
			} catch (Exception e1) {
			}
			Assert.assertTrue(formatLogMsg(" отсутствует"), res);
		}
		Assert.assertTrue(formatLogMsg(" отсутствует"), element != null);
	}

	/**
	 * Кликнуть по элементу
	 */
	public void click() {
		waitForIsElementPresent();
		info("Клик");
		browser.getDriver().getMouse().mouseMove(element.getCoordinates());
		/*
		 * if (element instanceof InternetExplorerElement) {
		 * browser.getDriver().getMouse().mouseDown(element.getCoordinates());
		 * Logger.getInstance().info("mouse down for IE"); }
		 */
		element.click();
	};

	/**
	 * Кликает по элементу без вывода информации в лог
	 */
	public void clickWithoutLog() {
		waitForIsElementPresent();
		browser.getDriver().getMouse().mouseMove(element.getCoordinates());
		element.click();
	};

	public void scroll() {
		element.click();
		element.sendKeys(Keys.ARROW_DOWN);
	}

	/**
	 * Кликнуть по элементу и дождаться загрузки страницы
	 */
	public void clickAndWait() {
		click();
		browser.waitForPageToLoad();
		browser.sleep();
	}

	/**
	 * Кликает и ждет появления нового окна
	 */
	public void clickAndWaitForNewWindow() {
		Set<String> set = browser.getDriver().getWindowHandles();
		int count = browser.getDriver().getWindowHandles().size();
		jsClick();
		info("Переключение на следующее окно");
		browser.waitForNewWindow(count);
		Iterator<String> cIter = browser.getDriver().getWindowHandles()
				.iterator();
		String handle = null;
		while (cIter.hasNext()) {
			String temp = cIter.next();
			if (!set.contains(temp)) {
				handle = temp;
				break;
			}
		}
		browser.getDriver().switchTo().window(handle);
		browser.windowMaximise();
	}

	/**
	 * Кликает и ждет появления нового окна
	 */
	public final void jsClickAndWaitForNewWindow() {
		Set<String> set = browser.getDriver().getWindowHandles();
		int count = browser.getDriver().getWindowHandles().size();
		jsClick();
		info("Переключение на следующее окно");
		browser.waitForNewWindow(count);
		Iterator<String> cIter = browser.getDriver().getWindowHandles()
				.iterator();
		String handle = null;
		while (cIter.hasNext()) {
			String temp = cIter.next();
			if (!set.contains(temp)) {
				handle = temp;
				break;
			}
		}
		browser.getDriver().switchTo().window(handle);
		browser.windowMaximise();
	}

	/**
	 * Кликает по элементу и ждет появления нового окна. Если после первого
	 * клика окно не появилось, то попытка повторяется. После пояления окна
	 * делается переключение на него.
	 * 
	 * @param quantityOfTrying
	 *            количество попыток
	 */
	public final void clickAndRepeatIfNewWindowNotAppeared(
			final Integer quantityOfTrying) {
		Set<String> set = browser.getDriver().getWindowHandles();
		int count = browser.getDriver().getWindowHandles().size();
		for (int i = 0; i < quantityOfTrying; i++) {
			// Иногда лбычным кликом не нажимает, в таком случае используем
			// другой метод
			if (i < 2) {
				click();
			} else if (i == 2) {
				clickExt();
			} else if (i == 2 + 1) {
				doubleClick();
			} else if (i > 2 + 2) {
				jsClick();
			}
			if (browser.waitForNewWindowWithoutAssertion(count)) {
				break;
			}
		}
		// Ждем последний раз, и если окно не открылось завершаем тест
		browser.waitForNewWindow(count);
		Iterator<String> cIter = browser.getDriver().getWindowHandles()
				.iterator();
		String handle = null;
		while (cIter.hasNext()) {
			String temp = cIter.next();
			if (!set.contains(temp)) {
				handle = temp;
				break;
			}
		}
		browser.getDriver().switchTo().window(handle);
	}

	/**
	 * Кликает по элементу и ждет закрытия окна. После переключается на
	 * предыдущее окно.
	 */
	public final void clickAndWaitForCloseWindow() {
		int count = browser.getDriver().getWindowHandles().size();
		click();
		info("Переключение на предыдущее окно");
		browser.waitForWindowIsCloseAndSwitch(count);
	}

	/**
	 * Кликает по элементу и ждет закрытия окна при помощи JS. После
	 * переключается на предыдущее окно.
	 */
	public final void jsClickAndWaitForCloseWindow() {
		int count = browser.getDriver().getWindowHandles().size();
		jsClick();
		info("Переключение на предыдущее окно");
		browser.waitForWindowIsCloseAndSwitch(count);
	}

	/**
	 * Делает клик по элементу и повторяет переданное количество раз.
	 * 
	 * @param countOfTrying
	 *            количество попыток до открытия нового окна
	 */
	public final void clickAndRepeatIfWndNotClosed(final int countOfTrying) {
		int count = browser.getDriver().getWindowHandles().size();
		for (int i = 0; i < countOfTrying; i++) {
			click();
			if (browser.waitForWndIsCloseWithoutAssertion(count)) {
				break;
			}
		}
		browser.waitForWindowIsCloseAndSwitch(count);
	}

	/**
	 * Возвращает текст внутри элемента
	 * 
	 * @return текст внутри элемента
	 */
	public final String getText() {
		assertIsPresent();
		return element.getText();
	}

	/**
	 * Проверяет текст элемента
	 * 
	 * @param text
	 *            текст
	 */
	public void assertText(String text) {
		assertIsPresent();
		String actual = getText();
		jassert.assertText(getName(), text, actual, true);
	}

	/**
	 * Проверяет что строка содержится в тексте ноды
	 * 
	 * @param exp
	 *            exp
	 */
	public final void assertTextContains(String exp) {
		waitAndAssertIsPresent();
		jassert.assertTextContains(getName() + " :: проверка наличия текста",
				exp, element.getText(), true);
	}

	/**
	 * Кликает по элементу с помощью Javascript
	 */
	public final void jsClick() {
		waitAndAssertIsPresent();
		info("Клик c помощью JS");
		((JavascriptExecutor) browser.getDriver()).executeScript(
				"arguments[0].click();", element);
	}

	/**
	 * Проверить наличие элемента на странице Если элемент отсутствует, то тест
	 * заверlается
	 */
	public void assertIsPresent() {
		if (!isPresent()) {
			fatal(" отсутствует");
		}
	}

	/**
	 * Проверяет, что элемент отсутствует на форме
	 */
	public void assertIsAbsent() {
		if (isPresent()) {
			fatal(" отсутствует");
		} else {
			info(" отсутствует на форме");
		}
	}

	/**
	 * Дождаться появления элемента и заверlить тест, если элемент не появился
	 */
	public void waitAndAssertIsPresent() {
		waitForIsElementPresent();
		assertIsPresent();
	}

	/**
	 * Проверить наличие элемента на странице
	 */
	public boolean isPresent() {
		WebDriverWait wait = new WebDriverWait(Browser.getInstance()
				.getDriver(), 2);

		try {
			wait.until((ExpectedCondition<Boolean>) new ExpectedCondition<Boolean>() {
				public Boolean apply(WebDriver driver) {
					try {
						List<WebElement> list = driver.findElements(locator);
						for (WebElement el : list) {
							if (el instanceof RemoteWebElement
									&& el.isDisplayed()) {
								element = (RemoteWebElement) el;
								if (element.isDisplayed())
									return element.isDisplayed();
							}
						}
						element = (RemoteWebElement) driver
								.findElement(locator);
					} catch (Exception e) {
						return false;
					}
					return element.isDisplayed();
				}
			});
		} catch (Exception e) {
		}
		try {
			return element.isDisplayed();
		} catch (Exception e) {
		}
		return false;
	}

	/**
	 * Двойной клик по элементу. Дожидается окончания рендерига
	 */
	public void doubleClick() {
		waitForIsElementPresent();
		info("Двойной клик");
		browser.getDriver().getMouse().mouseMove(element.getCoordinates());
		Actions builder = new Actions(browser.getDriver());
		Action dClick = builder.doubleClick(element).build();
		dClick.perform();
	}

	public void doubleThrowException() {
		waitForIsElementPresent();
		try {
			browser.getDriver().getMouse().mouseMove(element.getCoordinates());
			Actions builder = new Actions(browser.getDriver());
			Action dClick = builder.doubleClick(element).build();
			dClick.perform();
		} catch (Exception e) {
			logger.warn(e.toString());
		}
		info("Двойной клик");
	}

	/**
	 * another implementation of waiting
	 */
	public void exists() {
		new WebDriverWait(browser.getDriver(), Long.parseLong(browser
				.getTimeoutForCondition())) {
		}.until(new ExpectedCondition<Boolean>() {
			public Boolean apply(WebDriver driver) {
				return (driver.findElements(locator).size() > 0);
			}
		});
		// Logger.getInstance().info("element exists");
	}

	/**
	 * another implementation of waiting for not exists
	 */
	public void doesNotExist() {
		new WebDriverWait(browser.getDriver(), Long.parseLong(browser
				.getTimeoutForCondition())) {
		}.until(new ExpectedCondition<Boolean>() {
			public Boolean apply(WebDriver driver) {
				return (driver.findElements(locator).size() < 1);
			}
		});
		// Logger.getInstance().info("element does Not Exist");
	}

	public String getAttribute(String attr) {
		return browser.getDriver().findElement(locator).getAttribute(attr);
	}

	/**
	 * set value via javascript <b>document.getElementById('%1$s').value='%2$s'
	 * </b>
	 * 
	 * @param elementId
	 * @param value
	 */
	public void setValueViaJS(String elementId, String value) {
		try {
			((JavascriptExecutor) browser.getDriver()).executeScript(String
					.format("document.getElementById('%1$s').value='%2$s'",
							elementId, value), element);
		} catch (Exception r) {
			Logger.getInstance().warn(r.getMessage());
		}
	}

	/**
	 * Кликнуть по элементу правой кнопкой
	 */
	public void clickRight() {
		waitForIsElementPresent();
		info("Клик правой кнопкой");
		browser.getDriver().getMouse().mouseMove(element.getCoordinates());
		browser.getDriver().getMouse().contextClick(element.getCoordinates());
	}

	/**
	 * Фокусируется на элементе и эмулирует нажатие правой кнопки мыши
	 */
	public void focusAndClickRightFromAction() {
		waitForIsElementPresent();
		element.sendKeys(" ");
		browser.sleep();
		Actions a = new Actions(browser.getDriver());
		a.doubleClick(element);
		// a.contextClick(element);
		info("Клик правой кнопкой");
		// browser.getDriver().getMouse().mouseMove(element.getCoordinates());
		// browser.getDriver().getMouse().contextClick(element.getCoordinates());
	}

	/**
	 * Скролит, пока элемент невидимый
	 * 
	 * @return
	 */
	public void scrollToElement() {
		((JavascriptExecutor) browser.getDriver()).executeScript(
				"arguments[0].scrollIntoView();", element);
	}
}
